package com.bblocks.common.anti_reptile.rule;

import com.bblocks.common.anti_reptile.config.AntiReptileProperties;
import com.bblocks.common.anti_reptile.config.bean.RuleComm;
import com.bblocks.common.anti_reptile.constant.AntiReptileConsts;
import com.bblocks.common.anti_reptile.constant.RuleResultEnum;
import com.bblocks.common.anti_reptile.constant.RuleTypeEnum;
import com.bblocks.common.anti_reptile.module.IpUserNumVo;
import com.bblocks.common.anti_reptile.module.RuleStatVO;
import com.bblocks.common.anti_reptile.module.RuleVO;
import com.bblocks.common.anti_reptile.util.NamedThreadFactory;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Lazy;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * <p>Project: bb-anti-reptile - AbstractRuleData</p>
 * <p>描述：通用规则数据实现抽象-可以覆盖</p>
 * 1、提取数据层-需要执行实现
 * 2、数据缓存层-默认实现基于redison，二级缓存
 * @Author Zhao [125043150@qq.com]
 * @Date 2024/4/10 10:59
 * @Version 1.0
 * @since 8
 */
@Getter
@Slf4j
public abstract class AbstractRuleData implements RuleData {
    /**
     * url访问记录汇总
     */
    protected static final String sql_url_sum = "select * from sys_access_sum where `day` is null and id>? order by id asc limit ?";
    /**
     * ip汇总统计
     */
    protected static final String sql_ip_sum = "select * from sys_access_ip_sum where `day` is null and user_num>1 and id>? order by id asc limit ?";

    @Resource
    private AntiReptileProperties antiReptileProperties;

    @Lazy
    @Resource
    private RedissonClient redissonClient;

    /**
     * url频率数据Map
     */
    private  ConcurrentMap<String, RuleStatVO> urlRates ;
    /**
     * ip用户量Map
     */
    private  ConcurrentMap<String, Integer> ipUsers;

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1
            ,new NamedThreadFactory("rule-sync-task"));

    private static final LocalCachedMapOptions localCacheOptions = LocalCachedMapOptions.defaults()
            // 用于淘汰清除本地缓存内的元素
            // 共有以下几种选择:
            // LFU - 统计元素的使用频率，淘汰用得最少（最不常用）的。
            // LRU - 按元素使用时间排序比较，淘汰最早（最久远）的。
            // SOFT - 元素用Java的WeakReference来保存，缓存元素通过GC过程清除。
            // WEAK - 元素用Java的SoftReference来保存, 缓存元素通过GC过程清除。
            // NONE - 永不淘汰清除缓存元素。
            .evictionPolicy(LocalCachedMapOptions.EvictionPolicy.LRU)
            // 如果缓存容量值为0表示不限制本地缓存容量大小
            .cacheSize(1000)
            // 以下选项适用于断线原因造成了未收到本地缓存更新消息的情况。
            // 断线重连的策略有以下几种：
            // CLEAR - 如果断线一段时间以后则在重新建立连接以后清空本地缓存
            // LOAD - 在服务端保存一份10分钟的作废日志
            //        如果10分钟内重新建立连接，则按照作废日志内的记录清空本地缓存的元素
            //        如果断线时间超过了这个时间，则将清空本地缓存中所有的内容
            // NONE - 默认值。断线重连时不做处理。
            .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.CLEAR)
            // 以下选项适用于不同本地缓存之间相互保持同步的情况
            // 缓存同步策略有以下几种：
            // INVALIDATE - 默认值。当本地缓存映射的某条元素发生变动时，同时驱逐所有相同本地缓存映射内的该元素
            // UPDATE - 当本地缓存映射的某条元素发生变动时，同时更新所有相同本地缓存映射内的该元素
            // NONE - 不做任何同步处理
            .syncStrategy(LocalCachedMapOptions.SyncStrategy.INVALIDATE)
            // 每个Map本地缓存里元素的有效时间，默认毫秒为单位--10分钟
            .timeToLive(600, TimeUnit.SECONDS)
            // 每个Map本地缓存里元素的最长闲置时间，默认毫秒为单位--1分钟
            .maxIdle(60, TimeUnit.SECONDS);

    public void init(){
        urlRates = redissonClient.getLocalCachedMap(AntiReptileConsts.BB_ANTI_PRE + URL_RATE,localCacheOptions);
        ipUsers = redissonClient.getLocalCachedMap(AntiReptileConsts.BB_ANTI_PRE + IP_USERS,localCacheOptions);
    }


    /**
     * 同步全部
     * @param theDay 原则上是昨天（今天的数据应该是未能生成把）
     */
    public void allDatasSync(@NonNull LocalDate theDay) {
        RLock lock = redissonClient.getLock(AntiReptileConsts.BB_ANTI_PRE + RuleData.SYNC_LOCK);
        boolean lockResult = false;
        try {
            lockResult = lock.tryLock(1, 60 * 60, TimeUnit.SECONDS);//一个小时把
            if (lockResult) {

                RBucket<String> bucket = redissonClient.getBucket(AntiReptileConsts.BB_ANTI_PRE + RuleData.SYNC_DAY);
                String targetDay = bucket.get();
                String day = theDay.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
                if( targetDay != null && !targetDay.equals(day) ){//未执行过 或者 一样都不执行
                    log.info("同步数据日：{} 已同步!,本次不执行！",day);
                    return;
                }

                allDatas(theDay);

                //设置执行日志
                bucket.set(theDay.plusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd")));
            }else
                log.info("同步时间：{} 未抢到锁，本次不予执行",theDay);
        }catch (Exception e){
            log.error("同步数据异常：",e);
        }finally {
            if(lockResult)
                lock.unlock();//释放
        }
    }

    @Override
    public RuleVO theRule(@NonNull RuleTypeEnum ruleTypeEnum, @NonNull String url, String ip) {
        RuleComm rule = antiReptileProperties.ruleByType(ruleTypeEnum);
        if(rule == null )
            return null;
        if(ruleTypeEnum != RuleTypeEnum.IP_PLUS && ruleTypeEnum != RuleTypeEnum.USER && ruleTypeEnum != RuleTypeEnum.WHOLE_FLOW)
            return null;

        RuleStatVO statVO = urlRates.get(url);
        return Optional.ofNullable(statVO).map(vo->{
            Integer ratio = 1;
            if (ruleTypeEnum == RuleTypeEnum.IP_PLUS) {
                ratio = ipUsers.get(ip);
                if (ratio == null || ratio <= 0)
                    ratio = 1;
            }
            RuleResultEnum ruleResultEnum = RuleResultEnum.getRuleResult( statVO.limitTypeByRuleType(ruleTypeEnum) );
            return RuleVO.builder()
                    .type(ruleTypeEnum.getType())
                    .rateAvg((getValueByRuleType(ruleTypeEnum,statVO,ratio).intValue()))
                    .rateMax((int) (rule.getMultiplier() / 100.0 * getValueByRuleType(ruleTypeEnum,statVO,ratio)))
                    .ruleResultEnum(ruleResultEnum != null ? ruleResultEnum : RuleResultEnum.getRuleResult(rule.getLockType()))
                    .build();
        }).orElseGet(() ->{//默认值
            Integer defaultQps = rule.defaultQps();
            if(defaultQps == null){
                return null;
            }
            return RuleVO
                    .builder()
                    .type(ruleTypeEnum.getType())
                    .rateAvg(defaultQps)
                    .rateMax( (int) (rule.getMultiplier() * defaultQps / 100.0 ) )
                    .ruleResultEnum(RuleResultEnum.getRuleResult(rule.getLockType()))
                    .build();
        });
    }

    /**
     * 获取最终值
     * @param ruleTypeEnum
     * @param statVO
     * @param otherRatio 系统，目前除了ip的都应该是1
     * @return
     */
    private Double getValueByRuleType(RuleTypeEnum ruleTypeEnum,RuleStatVO statVO,Integer otherRatio){
        if(ruleTypeEnum == RuleTypeEnum.USER) {
            return statVO.getRateMaxUser() * otherRatio;
        }else if(ruleTypeEnum == RuleTypeEnum.IP_PLUS){
            return statVO.getRateMaxIp() * otherRatio;
        }else{
            return statVO.getRateAvgPlate() * otherRatio;
        }
    }

    /**
     * 覆盖值，忽略空值
     * @param ruleStatVOParam
     */
    @Override
    public void saveRule(@NonNull RuleStatVO ruleStatVOParam) {
        RuleStatVO ruleStatVO = urlRates.get(ruleStatVOParam.getUrl());
        if(ruleStatVO == null){
            urlRates.put(ruleStatVOParam.getUrl(),ruleStatVOParam);
        }else{//把非空的值覆盖

            if(ruleStatVOParam.getRateAvgIp() != null){
                ruleStatVO.setRateAvgIp(ruleStatVOParam.getRateAvgIp());
            }
            if(ruleStatVOParam.getRateMaxIp() != null){
                ruleStatVO.setRateMaxIp(ruleStatVOParam.getRateMaxIp());
            }


            if(ruleStatVOParam.getRateAvgUser() != null){
                ruleStatVO.setRateAvgUser(ruleStatVOParam.getRateAvgUser());
            }
            if(ruleStatVOParam.getRateMaxUser() != null){
                ruleStatVO.setRateMaxUser(ruleStatVOParam.getRateMaxUser());
            }

            if(ruleStatVOParam.getRateAvgPlate() != null){
                ruleStatVO.setRateAvgPlate(ruleStatVOParam.getRateAvgPlate());
            }
            if(ruleStatVOParam.getRateMaxPlate() != null){
                ruleStatVO.setRateMaxPlate(ruleStatVOParam.getRateMaxPlate());
            }


            if(ruleStatVOParam.getRateIpLimitType() != null){
                ruleStatVO.setRateIpLimitType(ruleStatVOParam.getRateIpLimitType());
            }
            if(ruleStatVOParam.getRateUserLimitType() != null){
                ruleStatVO.setRateUserLimitType(ruleStatVOParam.getRateUserLimitType());
            }
            if(ruleStatVOParam.getRatePlateLimitType() != null){
                ruleStatVO.setRatePlateLimitType(ruleStatVOParam.getRatePlateLimitType());
            }


            urlRates.put(ruleStatVOParam.getUrl(),ruleStatVO);
        }
    }

    /**
     *
     * @param userNoVo
     */
    @Override
    public void saveIpUser(@NonNull IpUserNumVo userNoVo) {
        if(userNoVo.getUserNum() !=null && userNoVo.getUserNum()>1) {//1以及以下的无意义
            ipUsers.put(userNoVo.getIp(), userNoVo.getUserNum());
        }
    }

    protected abstract void allDatas(@NonNull LocalDate theDay) throws Exception;



    /**
     * 获取运行（明天2点：AntiReptileProperties.syncTime ）
     * @return
     */
    protected LocalDateTime getRunStartTime(AntiReptileProperties antiReptileProperties){
        return LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(antiReptileProperties.getSync().getSyncTime()!=null?antiReptileProperties.getSync().getSyncTime() : 2 ,0,0));
    }

    protected long getRunStartTimeSeconds(AntiReptileProperties antiReptileProperties){
        LocalDateTime nextTime = getRunStartTime(antiReptileProperties);
        Duration duration = Duration.between(LocalDateTime.now(),nextTime); // 后面减去前面
        return duration.getSeconds();
    }

    /**
     * 是否启动就要同步一次
     * @return
     */
    protected boolean syncOnStart(){
        return  antiReptileProperties.getSync().isSyncOnStart();
    }


}
